package org.dromara.web.modbus.utils;

import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.domain.VariableAssociation;
import org.dromara.system.utils.DataStatusUtils;
import org.dromara.web.listener.AutoCloseableModbusMaster;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@RequiredArgsConstructor
public class Modbus4jUtils {

    private static final ReentrantLock lock = new ReentrantLock();
    private static final ModbusFactory MODBUS_FACTORY = new ModbusFactory();




    /**
     * 初始化 ModbusMaster
     */
    private static ModbusMaster initMaster(String host, int port) throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(host);
        params.setPort(port);
        ModbusMaster master = MODBUS_FACTORY.createTcpMaster(params, false);
        master.setTimeout(3000);
        master.init();
        return master;
    }

    /**
     * 单个变量读取
     */
    public static Number batchReadOne(VariableAssociation variable) throws Exception {
        String host = variable.getIp();
        int port = variable.getPort();
        int slaveId = variable.getSlaveId();
        int offset = Integer.parseInt(variable.getCode());

        try (AutoCloseableModbusMaster autoMaster = new AutoCloseableModbusMaster(host, port)) {
            Number value = readVariable(Math.toIntExact(variable.getVariableType()), host, port, offset, slaveId);
            DataStatusUtils.setJxzServerStatus(variable.getIp(),1);
            return value;
        } catch (Exception e) {
            DataStatusUtils.setJxzServerStatus(variable.getIp(),0);
            log.error("读取失败: {}", e.getMessage(), e);
            throw e;
        }
    }

    /**
     * 根据类型读取变量值
     */
    private static Number readVariable(int variableType, String host, int port, int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        switch (variableType) {
            case 1: // Coil
                return readModbusRegisterBoolean(host, port, offset, slaveId);
            case 2: // Holding Register
                return readModbusRegisterFloat(host, port, offset, slaveId);
            default: // Input Register
                return readInputRegister(host, port, offset, slaveId);
        }
    }

    /**
     * 读取 Coil 数据 (开关量)
     */
    private static Number readModbusRegisterBoolean(String host, int port, int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        ModbusMaster master = getMaster(host, port);
        BaseLocator<Boolean> locator = BaseLocator.coilStatus(slaveId, offset);
        return master.getValue(locator) ? 1.0 : 0.0;
    }

    /**
     * 读取 Holding Register 数据 (浮点数)
     */
    private static Number readModbusRegisterFloat(String host, int port, int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        lock.lock();
        try {
            ModbusMaster master = getMaster(host, port);
            BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, DataType.FOUR_BYTE_FLOAT);
            return master.getValue(locator);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 读取 Input Register 数据 (输入寄存器)
     */
    private static Number readInputRegister(String host, int port, int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        lock.lock();
        try {
            ModbusMaster master = getMaster(host, port);
            BaseLocator<Number> locator = BaseLocator.inputRegister(slaveId, offset, DataType.FOUR_BYTE_FLOAT);
            return master.getValue(locator);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取或创建 ModbusMaster
     */
    public static ModbusMaster getMaster(String host, int port) {
        try {
            return initMaster(host, port);
        } catch (ModbusInitException e) {
            log.error("ModbusMaster 初始化失败", e);
            throw new RuntimeException("ModbusMaster 初始化失败", e);
        }
    }

}
